﻿using System;
using System.Diagnostics.Contracts;
using System.Security.Cryptography;
using System.Text;
using O1.Data;

namespace O1
{
    [ContractVerification(true)]
    internal static class ParsingExtensions
    {
        public static unsafe short ToInt16(this byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Int16 <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                return *((short*)p);
            }
        }

        public static unsafe long ToInt64(this byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Int64 <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                return *((long*)p);
            }
        }

        public static unsafe void CopyBytes(this short value, byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Int16 <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                *((short*)p) = value;
            }
        }

        public static unsafe void CopyBytes(this long value, byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Int64 <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                *((long*)p) = value;
            }
        }

        public static unsafe int ToInt32(this byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Int32 <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                return *((int*)p);
            }
        }

        public static unsafe void CopyBytes(this int value, byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Int32 <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                *((int*)p) = value;
            }
        }

        public static unsafe uint ToUInt32(this byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Int32 <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                return *((uint*)p);
            }
        }

        public static unsafe void CopyBytes(this uint value, byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Int32 <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                *((uint*)p) = value;
            }
        }

        public static unsafe decimal ToDecimal(this byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Decimal <= buffer.Length);

            // Same technique used under the hood in System.BitConverter:
            fixed (byte* p = &buffer[offset])
            {
                return *((decimal*)p);
            }
        }

        public static unsafe void CopyBytes(this decimal value, byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Decimal <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                *((decimal*)p) = value;
            }
        }

        public static unsafe DateTime ToDateTime(this byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.DateTime <= buffer.Length);

            // Same technique used under the hood in System.BitConverter:
            fixed (byte* p = &buffer[offset])
            {
                return *((DateTime*)p);
            }
        }

        public static unsafe void CopyBytes(this DateTime value, byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.DateTime <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                *((DateTime*)p) = value;
            }
        }

        public static unsafe DateTimeOffset ToDateTimeOffset(this byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.DateTimeOffset <= buffer.Length);

            // Same technique used under the hood in System.BitConverter:
            fixed (byte* p = &buffer[offset])
            {
                return *((DateTimeOffset*)p);
            }
        }

        public static unsafe void CopyBytes(this DateTimeOffset value, byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.DateTimeOffset <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                *((DateTimeOffset*)p) = value;
            }
        }

        public static unsafe Guid ToGuid(this byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Guid <= buffer.Length);

            // Same technique used under the hood in System.BitConverter:
            fixed (byte* p = &buffer[offset])
            {
                return *((Guid*)p);
            }
        }

        public static unsafe void CopyBytes(this Guid value, byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Sz.CLR.Guid <= buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                *((Guid*)p) = value;
            }
        }

        public static unsafe Guid ToHashGuid(this string value)
        {
            // TODO: Distinguish null.
            if (string.IsNullOrEmpty(value))
            {
                return Guid.Empty;
            }

            using (var md5 = MD5.Create())
            {
                fixed (byte* p = md5.ComputeHash(Encoding.UTF8.GetBytes(value)))
                {
                    return *((Guid*)p);
                }
            }
        }
    }
}
